package com.evil.application.strategy.reporting;

import cn.hutool.core.collection.CollUtil;
import com.evil.application.enums.reporting.ReportingTypeEnum;
import com.evil.application.enums.reporting.SummaryTypeEnum;
import com.evil.application.pojo.dto.reporting.ApplicationReportingDTO;
import com.evil.application.pojo.dto.reporting.ReportingHeaderDTO;
import com.evil.application.pojo.dto.reporting.ReportingTargetDTO;
import com.evil.application.pojo.dto.reporting.data.*;
import com.evil.common.es.dto.Summary;
import com.evil.common.es.util.ElasticsearchUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.search.aggregations.Aggregations;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * 报表类型-接口
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
public interface ReportingDataChart {

    ReportingTypeEnum type();

    /**
     * 统计
     *
     * @param reportingDTO reportingDTO
     * @param aggregations aggregations
     * @return ReportingDataRespDTO
     */
    ReportingDataRespDTO calculation(ApplicationReportingDTO reportingDTO, Aggregations aggregations);

    default List<ReportingDataSeriesDTO> series(ApplicationReportingDTO reportingDTO, Aggregations aggregations) {
        List<ReportingDataSeriesDTO> series = new ArrayList<>();

        if (Objects.isNull(aggregations)) {
            return series;
        }

        BiFunction<SummaryTree, Object, ReportingDataSeriesValuesDTO> createSeriesValue = (tree, value) -> {
            ReportingDataSeriesValuesDTO seriesValuesDTO = new ReportingDataSeriesValuesDTO();
            seriesValuesDTO.setRowName(tree.getSummary().getValue());

            seriesValuesDTO.setValue(value);
            tree.getChildren().stream().findFirst().ifPresent(e -> {
                if (Objects.isNull(value)) {
                    TargetSummary targetSummary = (TargetSummary) e.getSummary();
                    ReportingTargetDTO target = targetSummary.getT();
                    seriesValuesDTO.setValue(target.formatDecimal(e.getSummary().getValue()));
                    seriesValuesDTO.setDisplayValue(target.format(e.getSummary().getValue()));
                }
            });
            return seriesValuesDTO;
        };

        // 处理指标
        BiConsumer<SummaryTree, List<ReportingDataSeriesValuesDTO>> handleTarget = (tree, seriesValueList) ->
                seriesValueList.add(createSeriesValue.apply(tree, null));

        Map<String, SummaryTree> secondValueMap = new LinkedHashMap<>();
        List<ReportingDataSeriesValuesDTO> seriesValues = new ArrayList<>();
        for (SummaryTree first : this.summaryTree(reportingDTO, aggregations).getChildren()) {
            if (SummaryType.ROW == first.getChildType()) {
                seriesValues = new ArrayList<>();
                for (SummaryTree second : first.getChildren()) {
                    // 记录值
                    secondValueMap.put(second.getSummary().getValue(), second);
                    handleTarget.accept(second, seriesValues);
                }
                ReportingDataSeriesDTO seriesDTO = new ReportingDataSeriesDTO(first.getSummary().getValue());
                seriesDTO.setSeriesValues(seriesValues);
                series.add(seriesDTO);
            } else {
                handleTarget.accept(first, seriesValues);
            }
        }

        // 未分类
        if (series.isEmpty()) {
            if (!seriesValues.isEmpty()) {
                ReportingDataSeriesDTO seriesDTO = new ReportingDataSeriesDTO("未分类");
                seriesDTO.setSeriesValues(seriesValues);
                series.add(seriesDTO);
            }
        } else {
            // 补充缺失分类
            series.forEach(s -> {
                Map<String, ReportingDataSeriesValuesDTO> seriesValuesMap = s.getSeriesValues().stream()
                        .collect(Collectors.toMap(ReportingDataSeriesValuesDTO::getRowName, e -> e));
                s.setSeriesValues(secondValueMap.entrySet().stream()
                        .map(entry -> seriesValuesMap.getOrDefault(entry.getKey(), createSeriesValue.apply(entry.getValue(), 0)))
                        .collect(Collectors.toList()));
            });
        }

        return series;
    }

    default List<List<ReportingTableDataDTO>> reportingHeaders(SummaryInfo summaryInfo) {
        List<String> rows = summaryInfo.rows.stream().map(HeaderSummary::getTitle).distinct().collect(Collectors.toList());
        Map<ReportingHeaderDTO, Set<String>> cellMap = new LinkedHashMap<>();
        for (HeaderSummary cell : summaryInfo.cells) {
            Set<String> values = cellMap.getOrDefault(cell.getT(), new HashSet<>());
            values.add(cell.getValue());
            cellMap.put(cell.getT(), values);
        }
        List<ReportingTargetDTO> targets = summaryInfo.targets.stream().map(Summary::getT).distinct().collect(Collectors.toList());

        // 表头有多少行（指标只占一行，一个列维度独占一行）
        int rowNum;
        if (cellMap.isEmpty()) {
            // 如果没有列维度，则有指标就固定2，没有指标就固定0
            rowNum = targets.isEmpty() ? 0 : 1;
        } else {
            // 如果有列维度，则有指标就 列维度数量 + 1，没有指标就 列维度数量 + 0
            rowNum = cellMap.size() + (targets.isEmpty() ? 0 : 1);
        }

        // 初始化返回
        List<List<ReportingTableDataDTO>> reportingHeaders = new ArrayList<>();

        // 处理行维度 第一行
        List<ReportingTableDataDTO> firstRow = rows.stream().map(e -> new ReportingTableDataDTO(e).setRowSpan(rowNum)).collect(Collectors.toList());
        // 处理列维度
        List<List<ReportingTableDataDTO>> cells = new ArrayList<>();

        int frequency = 1;
        List<Set<String>> cellList = new ArrayList<>(cellMap.values());
        for (int i = 0, l = cellList.size() - 1; i <= l; i++) {
            int columnSpan = 1;
            if (i != l) {
                for (int j = i + 1; j <= l; j++) {
                    Set<String> nextList = cellList.get(j);
                    if (!nextList.isEmpty()) {
                        columnSpan *= cellList.get(j).size();
                    }
                }
            }
            List<ReportingTableDataDTO> cellRow = new ArrayList<>();

            Set<String> cellValues = cellList.get(i);
            for (int ii = 0; ii < frequency; ii++) {
                for (String value : cellValues) {
                    cellRow.add(new ReportingTableDataDTO(value).setColumnSpan(columnSpan));

                    cellRow.addAll(ReportingTableDataDTO.generateEmptyCell(columnSpan - 1, true));
                }
            }
            frequency *= cellValues.size();
            cells.add(cellRow);
        }

        targets.forEach(target -> {
            SummaryTypeEnum summaryTypeEnum = SummaryTypeEnum.isExistById(target.getTotalType());
            // 指标有汇总方式
            boolean hasSummary = SummaryTypeEnum.NO.getId() != summaryTypeEnum.getId();
            // 指标合并格子数量
            int cellNum = cells.stream().max(Comparator.comparingInt(List::size)).map(List::size).orElse(0);
            if (hasSummary && cellNum != 0) {
                // 指标格子
                firstRow.add(new ReportingTableDataTargetDTO(target).setColumnSpan(cellNum + 1));
                // 补充指标合并格子
                firstRow.addAll(ReportingTableDataDTO.generateEmptyCell(cellNum, true));
            } else {
                // 指标格子
                firstRow.add(new ReportingTableDataTargetDTO(target));
            }
        });

        AtomicBoolean firstCell = new AtomicBoolean(true);
        for (List<ReportingTableDataDTO> cell : cells) {
            List<ReportingTableDataDTO> copyRow = new ArrayList<>();

            AtomicBoolean firstTarget = new AtomicBoolean(true);
            for (ReportingTargetDTO target : targets) {
                SummaryTypeEnum summaryTypeEnum = SummaryTypeEnum.isExistById(target.getTotalType());
                // 第一个指标需要 补充行维度的空格子
                if (firstTarget.get()) {
                    copyRow.addAll(ReportingTableDataDTO.generateEmptyCell(rows.size(), true));
                }
                // 每个指标复制一份列维度桶
                copyRow.addAll(ReportingTableDataDTO.copyList(cell));
                // 指标有小计
                if (SummaryTypeEnum.NO.getId() != summaryTypeEnum.getId()) {
                    if (firstCell.get()) {
                        // 多个列维度（表头有多行）只有，第一个列表的会显示 小计，下面全合并单元格
                        copyRow.add(new ReportingTableDataDTO(String.format("小计(%s)", summaryTypeEnum.getName()))
                                .setRowSpan(cells.size())
                                .setIsTotalColumn(true));
                    } else {
                        copyRow.add(new ReportingTableDataDTO().setCellHide(true).setIsTotalColumn(true));
                    }
                }
                firstTarget.set(false);
            }
            reportingHeaders.add(copyRow);
            firstCell.set(false);
        }

        reportingHeaders.add(0, firstRow);
        return reportingHeaders;
    }

    default SummaryInfo summaryInfo(ApplicationReportingDTO reportingDTO, Aggregations aggregations) {
        SummaryInfo summaryInfo = new SummaryInfo();

        List<Aggregations> aggregationsList = CollUtil.toList(aggregations);

        for (ReportingHeaderDTO header : reportingDTO.getReportingHeaders()) {
            HeaderSummary headerSummary = new HeaderSummary(header, null);
            List<Aggregations> currentAggs = new ArrayList<>();
            aggregationsList.forEach(aggs -> {
                Map<Summary<ReportingHeaderDTO>, Aggregations> map = ElasticsearchUtil.aggregationsData(headerSummary, aggs);
                currentAggs.addAll(map.values());
                summaryInfo.rows.addAll(map.keySet().stream().map(e -> (HeaderSummary) e).collect(Collectors.toList()));
            });
            aggregationsList = currentAggs;
        }

        for (ReportingHeaderDTO header : reportingDTO.getReportingColumnHeaders()) {
            HeaderSummary headerSummary = new HeaderSummary(header, null);
            List<Aggregations> currentAggs = new ArrayList<>();
            aggregationsList.forEach(aggs -> {
                Map<Summary<ReportingHeaderDTO>, Aggregations> map = ElasticsearchUtil.aggregationsData(headerSummary, aggs);
                currentAggs.addAll(map.values());
                summaryInfo.cells.addAll(map.keySet().stream().map(e -> (HeaderSummary) e).collect(Collectors.toList()));
            });
            aggregationsList = currentAggs;
        }

        for (ReportingTargetDTO target : reportingDTO.getReportingTargets()) {
            TargetSummary targetSummary = new TargetSummary(target, null);
            aggregationsList.forEach(aggs -> {
                Map<Summary<ReportingTargetDTO>, Aggregations> map = ElasticsearchUtil.aggregationsData(targetSummary, aggs);
                summaryInfo.targets.addAll(map.keySet().stream().map(e -> (TargetSummary) e).collect(Collectors.toList()));
            });
        }
        return summaryInfo;
    }

    default SummaryTree summaryTree(ApplicationReportingDTO reportingDTO, Aggregations aggregations) {
        SummaryTree rootSummaryTree = new SummaryTree(SummaryType.ROOT, new ReportingSummary(reportingDTO, null));

        Map<SummaryTree, Aggregations> aggregationsMap = new LinkedHashMap<>();
        aggregationsMap.put(rootSummaryTree, aggregations);

        Map<SummaryTree, List<SummaryTree>> reportingSummaryDataMap = new HashMap<>();
        reportingSummaryDataMap.put(rootSummaryTree, rootSummaryTree.children);

        for (int i = 0, l = reportingDTO.getReportingHeaders().size() - 1; i <= l; i++) {
            ReportingHeaderDTO header = reportingDTO.getReportingHeaders().get(i);
            HeaderSummary headerSummary = new HeaderSummary(header, null);
            aggregationsMap = this.summary(headerSummary, SummaryType.ROW, aggregationsMap, reportingSummaryDataMap);
        }

        for (int i = 0, l = reportingDTO.getReportingColumnHeaders().size() - 1; i <= l; i++) {
            ReportingHeaderDTO header = reportingDTO.getReportingColumnHeaders().get(i);
            HeaderSummary headerSummary = new HeaderSummary(header, null);
            aggregationsMap = this.summary(headerSummary, SummaryType.CELL, aggregationsMap, reportingSummaryDataMap);
        }

        for (ReportingTargetDTO target : reportingDTO.getReportingTargets()) {
            TargetSummary targetSummary = new TargetSummary(target, null);
            this.summary(targetSummary, SummaryType.TARGET, aggregationsMap, reportingSummaryDataMap);
        }

        return rootSummaryTree;
    }

    default Map<SummaryTree, Aggregations> summary(Summary<?> summary,
                                                   SummaryType type,
                                                   Map<SummaryTree, Aggregations> aggregationsMap,
                                                   Map<SummaryTree, List<SummaryTree>> reportingSummaryDataMap) {
        Map<SummaryTree, Aggregations> currentAggsMap = new LinkedHashMap<>();

        for (Map.Entry<SummaryTree, Aggregations> entry : aggregationsMap.entrySet()) {
            SummaryTree data = entry.getKey();
            data.childType = type;
            Aggregations aggs = entry.getValue();

            Map<? extends Summary<?>, Aggregations> map = ElasticsearchUtil.aggregationsData(summary, aggs);

            List<SummaryTree> children = reportingSummaryDataMap.get(data);
            map.forEach((key, childAggs) -> {
                SummaryTree childSummaryTree = new SummaryTree(type, key);
                children.add(childSummaryTree);
                reportingSummaryDataMap.put(childSummaryTree, childSummaryTree.children);

                currentAggsMap.put(childSummaryTree, childAggs);
            });
        }
        return currentAggsMap;
    }

    // region Summary
    @AllArgsConstructor
    enum SummaryType {
        ROOT, ROW, CELL, TARGET
    }

    class ReportingSummary extends Summary<ApplicationReportingDTO> {

        public ReportingSummary(ApplicationReportingDTO reportingDTO, String value) {
            super(reportingDTO, value);
        }

        @Override
        public String getTitle() {
            return this.getT().getReportingKey();
        }

        @Override
        public ReportingSummary copy() {
            return new ReportingSummary(this.getT(), this.getValue());
        }

        @Override
        public ReportingSummary copy(String value) {
            return new ReportingSummary(this.getT(), value);
        }
    }

    class HeaderSummary extends Summary<ReportingHeaderDTO> {

        public HeaderSummary(ReportingHeaderDTO headerDTO, String value) {
            super(headerDTO, value);
        }

        @Override
        public String getTitle() {
            return this.getT().getTitle();
        }

        @Override
        public HeaderSummary copy() {
            return new HeaderSummary(this.getT(), this.getValue());
        }

        @Override
        public HeaderSummary copy(String value) {
            return new HeaderSummary(this.getT(), value);
        }
    }

    class TargetSummary extends Summary<ReportingTargetDTO> {

        public TargetSummary(ReportingTargetDTO targetDTO, String value) {
            super(targetDTO, value);
        }

        @Override
        public String getTitle() {
            return this.getT().getKey();
        }

        @Override
        public TargetSummary copy() {
            return new TargetSummary(this.getT(), this.getValue());
        }

        @Override
        public TargetSummary copy(String value) {
            return new TargetSummary(this.getT(), value);
        }

        public String getValue() {
            if (StringUtils.isNotBlank(super.getValue())) {
                try {
                    return new BigDecimal(super.getValue()).stripTrailingZeros().toPlainString();
                } catch (Exception ignored) {
                }
            }
            return super.getValue();
        }
    }

    class SummaryTree {

        @Getter
        private final SummaryType type;
        @Getter
        private final Summary<?> summary;
        @Getter
        private final List<SummaryTree> children;
        @Getter
        private SummaryType childType;

        public SummaryTree(SummaryType type, Summary<?> summary) {
            this.type = type;
            this.summary = summary;
            this.children = new ArrayList<>();
        }
    }

    @Data
    class SummaryInfo {
        private List<HeaderSummary> rows;
        private List<HeaderSummary> cells;
        private List<TargetSummary> targets;

        public SummaryInfo() {
            this.rows = new ArrayList<>();
            this.cells = new ArrayList<>();
            this.targets = new ArrayList<>();
        }
    }
    // endregion
}
